/*
** Puzzle Structures by Neil C. Obremski                      [ Magenta's Maze ]
** 2017 Gibdon Moon Productions                   [ http://magsmaze.gibdon.com ]
**
** This is free and unencumbered software released into the public domain.
**
** Anyone is free to copy, modify, publish, use, compile, sell, or
** distribute this software, either in source code form or as a compiled
** binary, for any purpose, commercial or non-commercial, and by any
** means.
**
** In jurisdictions that recognize copyright laws, the author or authors
** of this software dedicate any and all copyright interest in the
** software to the public domain. We make this dedication for the benefit
** of the public at large and to the detriment of our heirs and
** successors. We intend this dedication to be an overt act of
** relinquishment in perpetuity of all present and future rights to this
** software under copyright law.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
** OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
** OTHER DEALINGS IN THE SOFTWARE.
**
** Tested w/ DOSBox 0.74, Microsoft C 5.10
 */
#ifndef __MAG_PUZZ
#define __MAG_PUZZ

#include <stdlib.h>		/* srand(), rand() */

#include "mag-cga.h"	/* colors */
#include "mag-cube.h"	/* panel model */
#include "mag-3d.h"		/* pane meshes and 3D circle/line drawing */
#include "mag-map.h"	/* pos */
#include "mag-math.h"	/* polynomials */

struct {
	char spell[9];		/* 8-letter name of spell being cast (srand) */
	unsigned int seed;	/* seed for random number generator (srand) */
	char solutions[3];	/* possible answers */
	char answers[3];	/* current answers */
	char x, y;			/* map coordinates of center */
	int cx, cz;			/* projected world coordinates of center */
	char sx[3], sy[3];	/* map coordinates of satellite circles */
	int px[3], pz[3];	/* projected world coordinates of satellite circles */
	int radius;			/* radius of map circle (MAP_W>>1 - 2) */
	int rotation;		/* rotation (degrees) of first satellite */
	int cenrot, satrot;	/* viewing rotation between center & first satellite */
	char equation[80];	/* equation (used as format string for sprintf) */
	struct mesh pane[3];/* pane per each circle (if circle is filled) */
	struct mesh m;		/* center panel */
} magic;

void drawmagic(void);
void movemagic(int,int,int, int);
void puzzle(char*, char);
char placecube(char value);
char solved(void);
void update_equation(void);
void viewmagic(void);

/* draws Magic Circles and Ley Lines */
void drawmagic(void)
{
	int i = 0;

	if (INT_ABS(pos.x - magic.x) <= 32 ||
		INT_ABS(pos.y - magic.y) <= 32) {
		project(&magic.m, TRUE);
		render(&magic.m);
		circley3d(magic.cx, -50, magic.cz, 50, MAGENTA);
	}

	for ( ; i < 3; i++) { /* loop through satellite circles */

		/* if leyline is over 32 tiles away then player can't possibly see it */
		if (INT_ABS(pos.x - magic.sx[i]) <= 32 ||
			INT_ABS(pos.y - magic.sy[i]) <= 32) {

			int x1 = magic.cx + MUL_SIN_DEG(50, magic.cenrot),
				z1 = magic.cz + MUL_COS_DEG(50, magic.cenrot),
				x2 = magic.px[i] + MUL_SIN_DEG(40, magic.satrot),
				z2 = magic.pz[i] + MUL_COS_DEG(40, magic.satrot);

			liney3d(x1,z1, x2,z2, -50, magic.answers[i] ? MAGENTA : WHITE);

			if (magic.answers[i]) {
				project(&magic.pane[i], TRUE);
				render(&magic.pane[i]);
			}

			circley3d(magic.px[i], -50, magic.pz[i], 40,
				magic.answers[i] ? MAGENTA : WHITE);
		}

		magic.cenrot += 120;
		if (magic.cenrot >= 360) {
			magic.cenrot -= 360;
		}

		magic.satrot += 120;
		if (magic.satrot >= 360) {
			magic.satrot -= 360;
		}

		magic.rotation += 120;
		if (magic.rotation >= 360) {
			magic.rotation -= 360;
		}
	}
}

/* translates/rotates world coordinates of magic circles */
void movemagic(int tx, int ty, int tz, int deg)
{
	int i;

	ty; /* ignore */

	if (tx || ty || tz) {
		magic.cx += tx;
		magic.cz += tz;
		translate(&magic.m, tx, 0, tz);
		for (i = 0; i < 3; i++) {
			magic.px[i] += tx;
			magic.pz[i] += tz;
			if (magic.answers[i]) {
				translate(&magic.pane[i], tx, 0, tz);
			}
		}
	}

	/* rotate with a +100 Z offset since view is 3rd person */
	if (deg) {
		int z = magic.cz - 100;

		translate(&magic.m, -magic.cx, 0, -magic.cz);
		rotatey(&magic.m, deg);

		rotatefx(&magic.cx, &z, deg);
		magic.cz = z + 100;

		translate(&magic.m, magic.cx, 0, magic.cz);

		magic.cenrot += deg;
		if (magic.cenrot >= 360) {
			magic.cenrot -= 360;
		}
		magic.satrot += deg;
		if (magic.satrot >= 360) {
			magic.satrot -= 360;
		}

		for (i = 0; i < 3; i++) {
			if (magic.answers[i]) {
				translate(&magic.pane[i], -magic.px[i], 0, -magic.pz[i]);
				rotatey(&magic.pane[i], deg);
			}

			z = magic.pz[i] - 100;
			rotatefx(&magic.px[i], &z, deg);
			magic.pz[i] = z + 100;

			if (magic.answers[i]) {
				translate(&magic.pane[i], magic.px[i], 0, magic.pz[i]);
			}
		}
	}
}

/* resets the magic based on a Spell Name */
void puzzle(char *spell, char walltile)
{
	int i, j, x, y;

	/* reset magic w/ map dimensions */
	memset(&magic, 0, sizeof(magic));
	if (MAP_W > MAP_H) {
		magic.radius = (MAP_H >> 1) - 3;
	} else {
		magic.radius = (MAP_W >> 1) - 3;
	}
	magic.x = (MAP_W >> 1);
	magic.y = (MAP_H >> 1);

	/* process spell into random number seed */
	for (j = 0, i = 0; j < 8; i++) {
		char c = spell[i];
		unsigned int m = 0;

		if (c >= 'A' && c <= 'Z') {
			m = (unsigned int)(c - 'A' + 1);
			magic.spell[j++] = c;
		} else if (c >= 'a' && c <= 'z') {
			m = (unsigned int)(c - 'a' + 1);
			magic.spell[j++] = c - 'a' + 'A';
		} else if (c >= '0' && c <= '9') {
			m = (unsigned int)(c - '0' + 'Z' + 1);
			magic.spell[j++] = c;
		} else if (!c) {
			break;
		} else {
			continue; /* ignore spaces/invalid characters */
		}

		magic.seed = magic.seed ? magic.seed * m : m;
	}

	if (j < 8) {
		j--;
		for (i = 7; j >= 0; j--,i--) {
			magic.spell[i] = magic.spell[j];
		}
		for ( ; i >= 0; i--) {
			magic.spell[i] = ' ';
		}
	}

	srand(magic.seed);

	/* randomly generate 3 roots to create a polynomial */
	magic.solutions[0] = (char)(1 + (rand() % 9));
	magic.solutions[1] = (char)(1 + (rand() % 9));
	magic.solutions[2] = (char)(1 + (rand() % 9));

	/* fill map with walls */
	clearmap(walltile);
	for (y = 2; y < MAP_H - 2; y++) {
		for (x = 2; x < MAP_W - 2; x++) {
			if (INT_ODD(y) || INT_ODD(x)) {
				map[y][x] = walltile;
			}
		}
	}

	/* randomly rotate in 10 degree increments and clear area around circles */
	magic.rotation = 10 * (rand() % 36);

#ifndef NDEBUG
	if (!stricmp(spell, "ROT180")) {
		magic.rotation = 180;
	} else if (!stricmp(spell, "ROT360")) {
		magic.rotation = 0;
	}
#endif

	x = magic.x; y = magic.y;
	map[y-1][x-1] = 0; map[y-1][0+x] = 0; map[y-1][x+1] = 0;
	map[0+y][x-1] = 0; map[0+y][0+x] = 0; map[0+y][x+1] = 0;
	map[y+1][x-1] = 0; map[y+1][0+x] = 0; map[y+1][x+1] = 0;

	for (i = 0; i < 3; i++) {
		int x = magic.x + MUL_SIN_DEG(magic.radius, magic.rotation),
			y = magic.y - MUL_COS_DEG(magic.radius, magic.rotation);

		magic.sx[i] = (char)(x);
		magic.sy[i] = (char)(y);

		map[y-1][x-1] = 0; map[y-1][0+x] = 0; map[y-1][x+1] = 0;
		map[0+y][x-1] = 0; map[0+y][0+x] = 0; map[0+y][x+1] = 0;
		map[y+1][x-1] = 0; map[y+1][0+x] = 0; map[y+1][x+1] = 0;

		magic.rotation += 120;
		if (magic.rotation >= 360) {
			magic.rotation -= 360;
		}
	}

	/* randomly generate maze using Binary Tree maze generation algorithm */
	for (y = 2; y < MAP_H; y += 2) {
		for (x = 2; x < MAP_W - 2; x += 2) {
			if (coinflip()) {			/* erase north "wall" */
				map[y-1][x] = 0;
			} else if (x + 1 < MAP_W) {	/* erase east "wall" */
				map[y][x+1] = 0;
			}
		}
	}

	/* randomly face player */
	pos.facing = 90 * (rand() % 4);
	pos.x = magic.x;
	pos.y = magic.y;

	/* randomly position 9 cubes */
	for (i = 1; i <= 9; i++) {
		/* choose coordinates */
		do {
			x = (1 + (rand() % (MAP_W - 2)));
			y = (1 + (rand() % (MAP_H - 2)));
		} while (map[y][x]);

		/* look up tile by value */
		for (j = 0; j < TILE_MAX; j++) {
			if (tiles[j].value == (char)i) {
				map[y][x] = (char)j;
				break;
			}
		}
	}

	/* repeated numbers automatically become answers */
	for (i = 1; i < 3; i++) {
		for (j = 0; j < 3; j++) {
			if (i == j) {
				continue;
			}
			if (magic.solutions[i] == magic.solutions[j]) {
				magic.answers[i] = magic.solutions[i];
				break;
			}
		}
	}

	/* generate equation string */
	update_equation();
}

/* attempts to place a cube at current position; returns TRUE on success */
char placecube(char value)
{
	int i = 0, j;

	for ( ; i < 3; i++) {
		if (pos.x == magic.sx[i] && pos.y == magic.sy[i]) {
			if (magic.answers[i]) { /* already answered */
				return FALSE;
			}

			for (j = 0; j < 3; j++) {
				if (value == magic.solutions[j]) {
					magic.answers[i] = value;
					update_equation();
					return TRUE;
				}
			}
		}
	}

	/* no circle found at position OR not one of the correct solutions */
	return FALSE;
}

/* returns TRUE if magic is solved */
char solved(void)
{
	int i, j;

	for (i = 0; i < 3; i++) { /* loop through player's answers */
		char correct = FALSE;

		if (!magic.answers[i]) {
			return FALSE;
		}

		for (j = 0; j < 3; j++) { /* compare to solutions */
			if (magic.answers[i] == magic.solutions[j]) {
				correct = TRUE;
				break;
			}
		}

		if (!correct) {
			return FALSE;
		}
	}

	return TRUE;
}

void update_equation(void)
{
	sprintf(magic.equation, "%s = (x-%c)(x-%c)(x-%c)",
		polynomial(roots(3,
			magic.solutions[0], magic.solutions[1], magic.solutions[2])),
		(magic.answers[0] ? magic.answers[0] + 48 : '?'),
		(magic.answers[1] ? magic.answers[1] + 48 : '?'),
		(magic.answers[2] ? magic.answers[2] + 48 : '?'));
}

void viewmagic(void)
{
	int i = 0;
	int viewrot = -pos.facing + 360; /* rotate world opposite */
	if (viewrot >= 360) {
		viewrot -= 360;
	}

	magic.cenrot = viewrot + magic.rotation;
	if (magic.cenrot >= 360) {
		magic.cenrot -= 360;
	}

	magic.satrot = magic.cenrot + 180;
	if (magic.satrot >= 360) {
		magic.satrot -= 360;
	}

	magic.cx = (magic.x - pos.x) * 100;
	magic.cz = (pos.y - magic.y) * 100;

	rotatefx(&magic.cx, &magic.cz, viewrot);

	magic.cz += 100;

	model_panel(&magic.m, 25, 50, 25);
	magic.m.stroke = MAGENTA;
	magic.m.fill = BLACK;
	magic.m.a = A_QUADT;
	texputc(&magic.m, 'M');
	rotatey(&magic.m, magic.cenrot);
	translate(&magic.m, magic.cx, 0, magic.cz);
	show(&magic.m);

	for ( ; i < 3; i++) { /* loop through satellite circles */
		int x = magic.x + MUL_SIN_DEG(magic.radius, magic.rotation),
			z = magic.y - MUL_COS_DEG(magic.radius, magic.rotation);

		magic.sx[i] = (char)(x); magic.sy[i] = (char)(z);

		/* translate to 3D world coordinates */
		x = (x - pos.x) * 100;
		z = (pos.y - z) * 100;

		rotatefx(&x, &z, viewrot);

		magic.px[i] = x;
		magic.pz[i] = z + 100;

		if (magic.answers[i]) { /* create 3D pane */
			model_panel(&magic.pane[i], 25, 50, 25);
			magic.pane[i].stroke = MAGENTA;
			magic.pane[i].fill = BLACK;
			magic.pane[i].a = A_QUADT;
			texputc(&magic.pane[i], (char)('0' + magic.answers[i]));
			rotatey(&magic.pane[i], magic.satrot + 180);
			translate(&magic.pane[i], x, 0, z + 100);
			show(&magic.pane[i]);
		}

		magic.satrot += 120;
		if (magic.satrot >= 360) {
			magic.satrot -= 360;
		}

		magic.rotation += 120;
		if (magic.rotation >= 360) {
			magic.rotation -= 360;
		}
	}
}

#endif
